package pkg

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"os/exec"
	"path/filepath"
	"strings"
)

type Config struct {
	Version  string  `yaml:"version"`
	Services ApiService `yaml:"services"`
}

type ApiService struct {
	Init   []*Service  `yaml:"init"`
	App    []*Service   `yaml:"app"`
}

func GetConfigFrom(filename string) (*Config, error) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	config := Config{}
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		return nil, err
	}
	return &config, nil
}

type Arg struct {
	Name string  `yaml:"name"`
	Type string  `yaml:"type"`
	Required bool `yaml:"required"`
	Default string `yaml:"default"`
	value interface{}
}

type Request []*Arg

func (req Request) strings() string {
	ret := ""
	for _, a := range req {
		if a.value != nil {
			ret = ret + " " + fmt.Sprintf("--%s=%v", a.Name, a.value)
		}
	}
	return ret
}

type Response struct {
	State  int
	Message string
	Payload interface{}
}

func ResponseError(status int, err error) Response {
	return Response{State:status, Message:err.Error()}
}

func ResponseErrorMessage(status int, msg string) Response {
	return Response{State:status, Message:msg}
}

func ResponsePayload(payload []byte) Response {
	return Response{State:200, Payload:payload}
}

type Service struct {
	Name     string `yaml:"name"`
	Method   string  `yaml:"method"`
	URI      string  `yaml:"uri"`
	Action   string  `yaml:"action"`
	Request  Request `yaml:"request"`
	Response Response
}

func (s *Service) getArgs(ctx *gin.Context) error {
	if s.Method == "POST" || s.Method == "UPLOAD" {
		for _, a := range s.Request {
			if a.Type == "file" {
				continue
			}
			val := ctx.PostForm(a.Name)
			if val == "" && a.Required{
				return fmt.Errorf("args %s is required", a.Name)
			}
			if val != "" {
				fmt.Println("get args ", a.Name, "=", val)
				a.value = val
			}
		}
	}
	if s.Method == "GET" {
		for _, a := range s.Request {
			val := ctx.Query(a.Name)
			if val == "" && a.Required {
				return fmt.Errorf("args %s is required", a.Name)
			}
			if val != "" {
				fmt.Println("get args ", a.Name, "=", val)
				a.value = val
			}
		}
	}
	return nil
}

func (c Config) ServiceInit(args []string) error  {
	kv := make(map[string]string)
	for _, arg := range args {
		vals := strings.Split(arg, "=")
		if len(vals) < 2 {
			return fmt.Errorf("invald args: " + arg)
		}
		kv[vals[0]] = vals[1]
	}


	for _, svc := range c.Services.Init {
		svcArgs := ""
		for _, req := range svc.Request {
			val, _ := kv[req.Name]
			if val == "" && req.Required {
				return fmt.Errorf("args %s is required", req.Name)
			}
			if val == "" && req.Default != "" {
				svcArgs = svcArgs + " " + fmt.Sprintf("--%s=%v", req.Name, req.Default)
			}
			if val != "" {
				svcArgs = svcArgs + " " + fmt.Sprintf("--%s=%v", req.Name, val)
			}
		}
		err := svc.run(svcArgs)
		if err != nil {
			return err
		}
	}
	return nil
}

func (s *Service) run(args string) error {
	log.Println("bash -c", s.Action, args)
	execCmd := fmt.Sprintf("%s %s", s.Action, args)
	cmd := exec.Command("bash", "-c", execCmd)
	output, err := cmd.CombinedOutput()
	fmt.Println(string(output))
	if err != nil {
		return err
	}
	return err
}

func (s *Service) execute(ctx *gin.Context) Response {
	err := s.getArgs(ctx)
	if err != nil {
		return ResponseError(400, err)
	}
	
	log.Println("bash -c", s.Action, s.Request.strings(), s.Name, s.URI)

	execCmd := fmt.Sprintf("%s %s", s.Action, s.Request.strings())
	cmd := exec.Command("bash", "-c", execCmd)
	output, err := cmd.CombinedOutput()
	fmt.Println(string(output))
	if err != nil {
		return ResponseError(500, err)
	}
	return ResponsePayload(nil)
}

func (s *Service) Execute(ctx *gin.Context) {
	resp := s.execute(ctx)
	ctx.JSON(resp.State, resp)
}

func (s *Service) Upload(ctx *gin.Context) {
	err := s.getArgs(ctx)
	if err != nil {
		ctx.JSON(400, ResponseError(400, err))
		return
	}
	path := ctx.PostForm("path")
	args := ""
	for _, arg := range s.Request {
		if arg.Type == "file" {
			log.Println("filename:", arg.Name)
			file, err := ctx.FormFile(arg.Name)
			if err != nil {
				log.Println("error:", err)
				ctx.JSON(500, ResponseError(500, err))
				return
			}
			log.Println("filepath base: ", filepath.Dir(file.Filename))
			err = ctx.SaveUploadedFile(file, file.Filename)
			if err != nil {
				ctx.JSON(500, ResponseError(500, err))
				return
			}
			args = args + " " + fmt.Sprintf("--%s=%v", arg.Name, file.Filename)
		}
		if arg.Type == "files" {
			form, err := ctx.MultipartForm()
			if err != nil {
				ctx.JSON(500, ResponseError(500, err))
				return
			}
			files := form.File[arg.Name]
			filesname := ""
			if len(files) > 0 {
				for _, file := range files {
					err = ctx.SaveUploadedFile(file, filepath.Join(path, file.Filename))
					if err != nil {
						ctx.JSON(500, ResponseError(500, err))
						return
					}
					if filesname == "" {
						filesname = file.Filename
					} else {
						filesname = filesname + "|" + file.Filename
					}
				}
				args = args + " " + fmt.Sprintf("--%s=%v", arg.Name, filesname)
			}
		}
	}


	args = args + " " + s.Request.strings()
	err = s.run(args)
	if err != nil {
		ctx.JSON(500, ResponseError(500, err))
		return
	}
	ctx.JSON(200, ResponsePayload(nil))
}